﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using JackyCode.Mock.Exceptions;

namespace JackyCode.Mock
{
    public class MockDataImpl : IMockData
    {
        private IDictionary<MethodData, MethodExcuteInfo> methodDatas = new Dictionary<MethodData, MethodExcuteInfo>();

        public void Merge(MethodData methodData, object returnVal)
        {
            if (!methodDatas.Keys.Contains(methodData))
            {
                methodDatas.Add(methodData, new MethodExcuteInfo { ExcuteReturnVal = returnVal, ExcuteTimes = 0 });
            }
            else
            {
                methodDatas[methodData] =  new MethodExcuteInfo { ExcuteReturnVal = returnVal, ExcuteTimes = 0 };
            }
        }

        private MethodExcuteInfo GetMethodExcuteInfo(MethodData methodData)
        {
            return methodDatas[methodData];
        }

        public object GetReturnVal(MethodData methodData)
        {
            if (methodData == null)
            {
                throw new ArgumentNullException("The methodData can't be null!");
            }

            if (!Contains(methodData))
            {
                return GetDefaultReturnVal(methodData);
            }

            SetMethodExcuteInfo(methodData);
            return GetMethodExcuteInfo(methodData).ExcuteReturnVal;
        }

        private void SetOutParameterInfo(MethodData methodData, MethodData realMethodData)
        {
            IList<ArgumentDetail> realAds = realMethodData.ArgumentDetails;
            for (int i = 0; i < realAds.Count; i++)
            {
                SetOutParameterVal(methodData, realAds, i);
            }
        }

        private void SetOutParameterVal(MethodData methodData, IList<ArgumentDetail> realAds, int index)
        {
            if (realAds[index].IsNotOut)
            {
                methodData.ArgumentDetails[index].Value = realAds[index].Value;
            }
        }

        public object GetReturnVal(MethodInfo methodInfo,IList<object> arguments)
        {
            return GetReturnVal(new MethodData(arguments,methodInfo));
        }

        private void SetMethodExcuteInfo(MethodData methodData)
        {
            GetMethodExcuteInfo(methodData).ExcuteTimes += 1;
        }

        private object GetDefaultReturnVal(MethodData methodData)
        {
            if (HasReturnVal(methodData))
            {
                if (methodData.MethodInfo.ReturnType.IsValueType)
                {
                    return Activator.CreateInstance(methodData.MethodInfo.ReturnType);
                }
            }
            return null;
        }

        private bool HasReturnVal(MethodData methodData)
        {
            return !methodData.MethodInfo.ReturnType.Equals(typeof(void));
        }

        public bool Contains(MethodData methodData)
        {
            return methodDatas.ContainsKey(methodData);
        }

        public int Count
        {
            get 
            {
                return methodDatas.Count;
            }
        }



        public int GetExecuteTimes(MethodData methodData)
        {
            if (!Contains(methodData))
            {
                throw new MockException("never setup method,can't do this operator!");
            }
            return GetMethodExcuteInfo(methodData).ExcuteTimes;
        }

        public object GetParameterVal(int pIndex,MethodInfo methodInfo,IList<object> arguments)
        {
            MethodData methodData = GetRealMethodData(methodInfo, arguments);
            if (methodData != null)
            {
                ArgumentDetail argumentDetail = methodData.ArgumentDetails[pIndex];
                return argumentDetail != null ? argumentDetail.Value : null;
            }
            return null;
        }

        private MethodData GetRealMethodData(MethodInfo methodInfo, IList<Object> arguments)
        {
            var results = methodDatas.Keys.Where(md => md.Equals(new MethodData(arguments, methodInfo)));
            return results.Count() > 0 ? results.First() : null;
        }

        public void SetOutParamterVal(MethodInfo methodInfo, IList<object> arguments)
        {
            MethodData realMethodData = GetRealMethodData(methodInfo, arguments);

            SetOutParameterVal(arguments, realMethodData.ArgumentDetails);

        }

        private void SetOutParameterVal(IList<object> arguments, IList<ArgumentDetail> ads)
        {
            for (int index = 0; index < ads.Count(); index++)
            {
                if (!ads[index].IsNotOut)
                {
                    arguments[index] = ads[index].Value;
                }
            }
        }

    }
}
